/****************************************************************************/
/* Copyright 2008 Jesse E. H-K                                              */
/*                                                                          */
/* This file is part of Icepac.                                             */
/*                                                                          */
/* Icepac is free software: you can redistribute it and/or modify           */
/* it under the terms of the GNU General Public License as published by     */
/* the Free Software Foundation, either version 3 of the License, or        */
/* (at your option) any later version.                                      */
/*                                                                          */
/* Icepac is distributed in the hope that it will be useful,                */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of           */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
/* GNU General Public License for more details.                             */
/*                                                                          */
/* You should have received a copy of the GNU General Public License        */
/* along with Icepac.  If not, see <http://www.gnu.org/licenses/>.          */
/****************************************************************************/

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <ctype.h>

#include <libgen.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

#include "libicepac.h"

/* Buffer for error messages. Primarily so that sscanf can
 * be used without having to declare a buffer in every function.
 */
static char errbuff[ICEPAC_ERRBUFF];

static char *builderrstr( const char *fmt, ... ) {
    va_list argp;
    
    va_start( argp, fmt );
    vsprintf( errbuff, fmt, argp );
    return errbuff;
}

static int ishidden( const char *filename ) {
    return *filename == '.';
}

/*
 * Given a luaL_Buffer, and the number of "parts" of the
 * path, will store the resulting path in the buffer. For example,
 *
 * buildpath( buff, 3, "/", "home", "joe" ) => "/home/joe"
 */
static void buildpath( luaL_Buffer *buff, int nparts, ... ) {
    va_list argp;
    int i;
    char *part = NULL;
    
    va_start( argp, nparts );
    for ( i = 0; i < nparts; i++ ) {
        part = va_arg( argp, char * );
        
        luaL_addstring( buff, part );
        
        if ( (i != nparts - 1) && (part[strlen( part ) - 1] != '/') )
            luaL_addchar( buff, '/' );
    }
    
    va_end( argp );
}

/* Copy a file from src to dest, char-by-char.
 * TODO: Does this also work for binary files? Is there
 * a better way to do this?
 */
static void cp( FILE *src, FILE *dest ) {
    int ch;
    
    while ( (ch = fgetc( src )) != EOF )
        fputc( ch, dest );
}

int icepac_cpfile( lua_State *L ) {
    const char *src = NULL;
    const char *dest = NULL;
    FILE *sf = NULL;
    FILE *df = NULL;
    
    src = luaL_checkstring( L, -2 );
    dest = luaL_checkstring( L, -1 );
    
    if ( ((sf = fopen( src, "r" )) == NULL) ||
         ((df = fopen( dest, "w")) == NULL) ) {
        lua_pushnil( L );
        lua_pushstring( L, builderrstr( "%s", strerror( errno ) ) );
        return 2;
    }
    
    cp( sf, df );
    
    fclose( sf );
    fclose( df );
    lua_pushboolean( L, 1 );
    return 1;
}

int icepac_mkdir( lua_State *L ) {
    const char *location = NULL;
    
    location = luaL_checkstring( L, -1 );
    if ( mkdir( location, S_IRWXU ) ) {
        lua_pushnil( L );
        lua_pushstring( L, builderrstr( "%s: %s", "Can't create directory", strerror( errno ) ) );
        return 2;
    }
    
    lua_pushboolean( L, 1 );
    return 1;
}

/* These functions define userdata
 * and appropriate gc and iterator functions
 * for the iterdir iterator.
 */

#define ICEPAC_DIRPATH_MT "icepac.dirpath"

typedef struct dirpath {
    DIR *dir;
    char fullpath[ICEPAC_DIRMAX];
} dirpath;

static int dirpath_iter( lua_State *L ) {
    dirpath *dp = NULL;
    struct dirent *ent = NULL;
    luaL_Buffer buff;
    
    dp = lua_touserdata( L, lua_upvalueindex( 1 ) );
    luaL_buffinit( L, &buff );
    
    /* Read values until
     * one is not hidden.
     * If there are no more entries, return nothing.
     */
    while ( 1 ) {
        if ( (ent = readdir( dp->dir )) != NULL ) {
            if ( ! ishidden( ent->d_name ) ) {
                buildpath( &buff, 2, dp->fullpath, ent->d_name );
                luaL_pushresult( &buff );
                return 1;
            } else
                continue;
        } else
            break;
    }
    
    return 0; /* No more values */
}

static int dirpath_gc( lua_State *L ) {
    dirpath *dp = NULL;
    
    dp = lua_touserdata( L, -1 );
    if ( dp )
        closedir( dp->dir );
    
    return 0;
}

int icepac_iterdir( lua_State *L ) {
    const char *filename = NULL;
    dirpath *dp = NULL;
    
    filename = luaL_checkstring( L, -1 );
    dp = lua_newuserdata( L, sizeof( *dp ) );
    
    strcpy( dp->fullpath, filename );
    
    if ( (dp->dir = opendir( filename )) == NULL ) {
        lua_pushnil( L );
        lua_pushstring( L, builderrstr( "%s: %s", filename, strerror( errno ) ) );
        return 2;
    }
    
    /* Set its metatable */
    luaL_getmetatable( L, ICEPAC_DIRPATH_MT );
    lua_setmetatable( L, -2 );
    
    lua_pushcclosure( L, dirpath_iter, 1 );
    return 1;
}
/* End iterdir iterator functions */

int icepac_listdir( lua_State *L ) {
    luaL_Buffer buff;
    DIR *dir = NULL;
    struct dirent *ent = NULL;
    const char *filename = NULL;
    int i;
    
    filename = luaL_checkstring( L, -1 );
    if ( (dir = opendir( filename )) == NULL ) {
        lua_pushnil( L );
        lua_pushstring( L, builderrstr( "%s: %s", filename, strerror( errno ) ) );
        return 2;
    }
    
    lua_newtable( L );
    i = 1;
    while ( (ent = readdir( dir )) != NULL ) {
        if ( ! ishidden( ent->d_name ) ) {
            /* Push key, value */
            lua_pushnumber( L, i++ );
            
            luaL_buffinit( L, &buff );
            buildpath( &buff, 2, filename, ent->d_name );
            luaL_pushresult( &buff );
            
            lua_settable( L, -3 );
        }
    }
    
    closedir( dir );
    return 1;
}


int icepac_basename( lua_State *L ) {
    char buffer[ICEPAC_DIRMAX];
    
    strcpy( buffer, luaL_checkstring( L, -1 ) );
    lua_pushstring( L, basename( buffer ) );
    return 1;
}

int icepac_isdir( lua_State *L ) {
    const char *filename = NULL;
    struct stat s;
    
    filename = luaL_checkstring( L, -1 );
    if ( stat( filename, &s ) ) {
        lua_pushnil( L );
        lua_pushstring( L, builderrstr( "%s: %s", filename, strerror( errno ) ) );
        return 2;
    }
    
    if ( S_ISDIR( s.st_mode ) )
        lua_pushboolean( L, 1 );
    else
        lua_pushboolean( L, 0 );
    
    return 1;
}

static const struct luaL_reg icepac[] = {
    { "listdir", icepac_listdir },
    { "iterdir", icepac_iterdir },
    { "isdir", icepac_isdir },
    { "basename", icepac_basename },
    { "mkdir", icepac_mkdir },
    { "cpfile", icepac_cpfile },
    { NULL, NULL }
};

int luaopen_libicepac( lua_State *L ) {
    /*
     * Set the metatable for the dir userdata
     */
    luaL_newmetatable( L, ICEPAC_DIRPATH_MT );
    lua_pushstring( L, "__gc" );
    lua_pushcfunction( L, dirpath_gc );
    lua_settable( L, -3 );
    
    luaL_register( L, "icepac", icepac );
    
    return 1;
}
